/**
 * @file session.c
 * @brief Implementa le funzioni per comunicare e gestire le sessioni con l'utente.
 *
 * Contiene le funzioni dell'entità HostSession utilizzata per indicare le varie modalità di interazione
 * con l'utente.
 *
 * @date Mar 18, 2012
 * @author Francesco Corvino
 */
#include "session.h"
#include "../logproxy/logproxy.h"
#include <unistd.h>
#include <pthread.h>

static int _Session_lastKey = 0;

/**
 * @brief Funzione privata che permette di ordinare le threads registrate per una sessione.
 */
int _Session_orderThreads(const void *a, const void *b){
	int A,B;
	if((a==NULL) && (b==NULL)) return 0;
	if(a!=NULL) A = ((ElaborationEntity*) a)->keyElaboration; else return -1;
	if(b!=NULL) B = ((ElaborationEntity*) b)->keyElaboration; else return 1;
	return (A>B)?(1):((A==B)?0:-1);
}

void _Session_initSyncFor(pthread_mutex_t* mtx){
	int res = pthread_mutex_init(mtx,NULL);
	if(res!=0){
		log_level(LOG_ERROR_LEVEL, "impossibile avviare le operazioni di sincronizzazione");
	}
}

/**
 * @brief Inizializza un'entità sessione.
 *
 * @param session Puntatore alla sessione da inizializzare.
 * @param getReadFd Funzione che ricava il file descriptor in lettura.
 * @param getWriteFd Funzione che ricava il file descriptor in scrittura.
 * @param update
 * @param close
 */
void Session_init(HostSession* session,
		int (*getReadFd)(HiddenType*), int (*getWriteFd)(HiddenType*),
		void (*update)(), void (*close)(HiddenType*)
		)
{
	/// - keySession per default uguale ad una variabile int statica
	session->keySession = _Session_lastKey;
	/// - si specifica le proprietà dell'array di elaborazioni
	DynArray_init(&(session->threadsForSession),
			_Session_orderThreads,
			sizeof(ElaborationEntity));
	session->threadsForSession.destroy = NULL;
	/// - per default la sessione è inizializzata non in attesa
	session->isWaiting = 0;
	/// - si inizializzano gli attributi di una sessione
	CMap_init(&(session->attributes));
	/// - si assegnano le proprietà della sessione
	session->getReadFd = getReadFd;
	session->getWriteFd = getWriteFd;
	session->update = update;
	session->close = close;
	/// - vengono inizializzati i mutex per la sincronizzazione delle risorse
	_Session_initSyncFor(&(session->syncReads));
	_Session_initSyncFor(&(session->syncWrites));
	_Session_initSyncFor(&(session->syncElaborations));
	/// - infine viene incrementata la variabile statica contatore delle sessioni create
	_Session_lastKey++;
}

/**
 * @brief Controlla che una sessione abbia delle elaborazioni.
 *
 * Se una sessione non ha elaborazioni significa che è in attesa di eseguire un comando.
 *
 * @param session puntatore alla sessione da controllare.
 * @return true se la sessione ha almeno un'elaborazione.
 */
bool Session_hasElaboration(HostSession* session){
	bool ret = true;
	pthread_mutex_lock(&(session->syncElaborations));
	if(session->threadsForSession.numelement==0) ret = false;
	pthread_mutex_unlock(&(session->syncElaborations));
	return ret;
}

/**
 * @brief Restituisce il file descriptor in lettura della sessione.
 *
 * @param session puntatore alla sessione da controllare.
 * @return file descriptor in lettura per la sessione passata.
 */
int Session_getFDRead(HostSession* session){
	int (*fn)(HiddenType*) = session->getReadFd;
	if(fn!=NULL) return (*fn)(&(session->dataSession));
	else return STDIN_FILENO;
}

/**
 * @brief Restituisce il file descripton in scrittura della sessione.
 *
 * @param session puntatore alla sessione da controllare.
 * @return file descriptor in scrittura per la sessione passata.
 */
int Session_getFDWrite(HostSession* session){
	int (*fn)(HiddenType*) = session->getWriteFd;
	if(fn!=NULL) return (*fn)(&(session->dataSession));
	return STDOUT_FILENO;
}

/**
 * @brief Legge dalla sessione.
 *
 * La lettura e la scrittura dal canale necessitano della sincronizzazione, perciò in questa
 * chiamata si effettua il controllo del mutex in lettura prima di effettuare l'operazione.
 *
 * @param session puntatore alla sessione da controllare.
 * @param buf puntatore su cui scrivere i byte ricevuti.
 * @param size grandezza massima che è possibile scrivere in buf.
 * @return numero di byte letti.
 */
int Session_Read(HostSession* session,char *buf, int size){
	int byteRead = 0;
	int fd = Session_getFDRead(session);
	pthread_mutex_lock(&(session->syncReads));
	byteRead = read(fd,buf,size);
	pthread_mutex_unlock(&(session->syncReads));
	return byteRead;
}

/**
 * @brief Scrive nella sessione.
 *
 * @param session puntatore alla sessione da controllare.
 * @param buf puntatore alla stringa da scrivere.
 * @param size grandezza massima che è possibile scrivere da buf.
 * @return numero di byte scritti.
 */
int Session_Write(HostSession* session,char *buf, int size){
	log_namedlevel(LOG_SESSION_NAME,LOG_DEBUG_LEVEL,
			"W Session:%d byte:%d",session->keySession, size);
	int byte = 0;
	int fd = Session_getFDWrite(session);
	pthread_mutex_lock(&(session->syncWrites));
	byte = write(fd,buf,size);
	pthread_mutex_unlock(&(session->syncWrites));
	return byte;
}

void Session_Update(HostSession* session){
	int iElab=0;ElaborationEntity * elab;
	DYNARRAY_FOREACH(&(session->threadsForSession),&iElab,elab){
		if(elab->notify!=NULL) {
			(*(elab->notify))(session,NULL);
		}
	}
}

void Session_addMyElaboration(HostSession* session){
	ElaborationEntity newElab;
	newElab.keyElaboration = pthread_self();
	newElab.notify = NULL;
	Session_addElaboration(session, &newElab);
}

/**
 * @brief Aggiunge un'elaborazione generica.
 *
 * @param session
 * @param elab
 */
void Session_addElaboration(HostSession* session, ElaborationEntity * elab){
	pthread_mutex_lock(&(session->syncElaborations));
	DynArray_add(&(session->threadsForSession),elab);
	pthread_mutex_unlock(&(session->syncElaborations));
}


void Session_delMyElaboration(HostSession* session){
	pthread_t tid = pthread_self();
	Session_delElaborationFor(session, tid);
}

void Session_delElaborationFor(HostSession* session, pthread_t tid){
	ElaborationEntity delElab;
	delElab.keyElaboration = tid;
	pthread_mutex_lock(&(session->syncElaborations));
	DynArray_remove(&(session->threadsForSession),&delElab);
	pthread_mutex_unlock(&(session->syncElaborations));
}

void Session_lockSession(HostSession* session){
	pthread_mutex_lock(&(session->syncElaborations));
}

void Session_unlockSession(HostSession* session){
	pthread_mutex_unlock(&(session->syncElaborations));
}
